/**
 * 
 */
package android.remotematrixclient.objclient;

import android.remotematrixclient.objclient.clientevent.ObjEvent;
import android.remotematrixclient.objclient.clientevent.ObjEventListener;

import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;


/**
 * La clase final <code> ObjClient</code> es la encargada de las comunicaciones
 * en la aplicación <b>RemoteMatrixClient</b>. Al ser un objeto de tipo final
 * no es posible crear varias instancias de este objeto, el cual se instancia
 * solo al ejecutar el método <code>init()</code>.
 * 
 * Este objeto establece los parámetros de comunicación con el servicio de
 * ejecución remoto. Implementa la interfase <code>ObjEventListener </code> 
 * para obtener los eventos del objeto <code>Receiver</code>.
 * 
 * El objeto <code>ObjClient</code> notifica a otros objetos que implementan la
 * interfase <code>ObjEventListener</code> cuando los mensajes son recibidos en
 * el atributo <code>ircv</code>.
 * 
 * Este objeto también lleva el control de la cantidad de unidades de proceso
 * que se encuentran disponibles y de las que se están utilizando dentro de la 
 * aplicación. Evita que una aplicación solicite más procesadores de los que 
 * están disponibles en el servidor.
 * 
 * @author Rafael Asprilla
 *
 */
public final class ObjClient implements ObjEventListener{

	/**
	 * <code>Socket</code> que se genera entre el servidor y este cliente.
	 */
	private static Socket conn;
	
	/**
	 * Servicio de envío de objetos.
	 */
	private Sender osnd;
	
	/**
	 * Servicio de recepción de objetos.
	 */
	private Receiver ircv;
	
	/**
	 * Lista de objetos registrados para recibir notificaciones de recepción de
	 * mensajes.
	 */
	private static List<ObjEventListener> listener;
	
	/**
	 * Instancia privada de este objeto. Evita que existan otras instancias de 
	 * este objeto.
	 */
	private static ObjClient objclient;
	
	/**
	 * Número de unidades de proceso actualmente utilizados por las 
	 * aplicaciones.
	 */
	private int cpus;
	
	/**
	 * Número máximo de unidades de proceso disponibles en el servidor de
	 * ejecución remota. Ninguna aplicación puede reservar más unidades de
	 * proceso de las que se registran en este atributo. Debe enviarse un
	 * commando "CPU" periódicamente para verificar si este número ha cambiado.
	 * Esto es posible si se cambia de servidor, saliendo de una zona de 
	 * cobertura y entrando en otra dentro de la nube distribuida.
	 */
	private int max_cpus;
	
	/**
	 * Constructor único de la clase <code>ObjClient</code>.
	 * @param address
	 * Dirección del servidor de ejecución remota.
	 * @param port
	 * Puerto del servidor de ejecución remota.
	 */
	private ObjClient(String address, int port){		
		try {
			conn = new Socket(address, port);			
			osnd = new Sender(conn);
			ircv = new Receiver(conn);
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		ircv.addListener(this);
		
		new Thread(ircv).start();
		listener = new ArrayList<ObjEventListener>();	
		cpus = 0;
	}
	
	/**
	 * El método <code>init(String, int)</code> se encarga de inicializar una 
	 * única instancia de la clase <code>ObjClient</code> y obtiene la cantidad
	 * de unidades de proceso que están disponibles dentro del servidor remoto.
	 * 
	 * @param address
	 * Dirección del servidor de ejecución remota.
	 * @param port
	 * Puerto del servidor de ejecución remota.
	 */
	public static void init(String address, int port) {	
		objclient = new ObjClient(address, port); 
		ObjClient.getInstance().CPUS();
	}
	
	/**
	 * @return
	 * Devuelve una instancia del objeto <code>ObjClient</code> para propósitos 
	 * de administración y ejecución de métodos. Sólo se pueden ejecutar 
	 * métodos de esta clase mediante la ejecución de este método. 
	 */
	public static ObjClient getInstance(){
		return objclient;
	}
	
	/**
	 * Registra un objeto para ser notificado de recepción de mensajes.
	 * 
	 * @param l
	 * Objeto tipo <code>ObjEventListener</code> a ser registrado.
	 */
	public synchronized void addListener(ObjEventListener l){
		synchronized(listener){
			listener.add(l);
		}
		
	}
	
	/**
	 * Remueve el registro de la lista de objetos registrados para notificación
	 * de eventos de recpción de mensajes.
	 * 
	 * @param l
	 *  Objeto tipo <code>ObjEventListener</code> a ser removido.
	 */
	public synchronized void removeListener(ObjEventListener l){
		synchronized(listener){
			listener.remove(l);
		}
	}
	
	/**
	 * El método trigger notifica a todos los objetos registrados en este
	 * objeto de que ha ocurrido un evento de tipo <code>ObjEvent</code>
	 * y pasa la referencia al mensaje recibido para que pueda ser procesado.
	 * 
	 * @param cw
	 * Objeto de mensaje recibido.
	 */
	private synchronized static void trigger(CommandWrap cw){
		ObjEvent evt = new ObjEvent(ObjClient.getInstance(), cw);
		synchronized(listener){
			ListIterator<ObjEventListener> i = listener.listIterator();
			while(i.hasNext()){
				((ObjEventListener)i.next()).handleClientEvent(evt);
			}
		}
	}
	
	/**
	 * Cierra las conexiones de este objeto.
	 */
	public void close(){
		osnd.close();
		ircv.close();
		try {
			conn.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * El método <code>request(String, Object)</code> envía los comandos de 
	 * ejecución al servidor remoto, adjunta los datos requeridos para que
	 * pueda completarse satisfactoriamente.
	 * 
	 * @param cmd
	 * Comando a ser ejecutado por el servidor remoto, también puede ser un 
	 * mensaje de acuse de recibo.
	 * @param data
	 * Datos a ser enviados junto con el comando.
	 * @return
	 * Devuelve el número de identificación de la transacción generada.
	 */
	private synchronized int request(String cmd, Object data){
		CommandWrap cw = new CommandWrap(cmd, data);
		osnd.set(cw);
		return cw.id;
	}
	
	/**
	 * Permite a otros objetos hacer peticiones de almacenamiento de objetos
	 * dentro del servidor remoto.
	 * 
	 * @param obj
	 * Recibe el objeto a ser enviado.
	 * @return
	 * Devuelve el número de identificación de la transacción generada.
	 */
	public int PUT(Object obj){
		return objclient.request("PUT", obj);
	}
	
	/**
	 * Permite descargar objetos a partir de un nombre de archivo desde el
	 * servidor remoto.
	 * 
	 * @param filename
	 * Nombre del archivo donde se encuentra almacenado el objeto a ser 
	 * descargado.
	 * @return
	 * Devuelve el número de identificación de la transacción generada.
	 */
	public int GET(String filename){
		return objclient.request("GET", filename);
	}
	
	/**
	 * Permite la ejecución remota de la cadena de comandos que se pasa como 
	 * argumento. El argumento de este método puede ser una cadena xml o 
	 * simplemente una cadena con el comando a ser ejecutado.
	 * 
	 * @param command
	 * Cadena con el contenido del comando a ser ejecutado
	 * @return
	 * Devuelve el número de identificación de la transacción generada.
	 */
	public int EXE(String command){
		return objclient.request("EXE", command);
	}
	
	/**
	 * Solicita la cantidad máxima de procesadores disponibles en el servidor
	 * remoto.
	 * 
	 * @return
	 * Devuelve el número de identificación de la transacción generada.
	 */
	public int CPUS(){
		return objclient.request("CPUS", null);
	}
	
	/**
	 * Define la cantidad de procesadores que se le solicitará al servidor 
	 * remoto en cada operación ejecución.
	 * 
	 * @param prcs
	 * Número de procesadores solicitados. De ser mayor al número máximo 
	 * disponible, se mantiene el valor anterior que fue solicitado.
	 */
	public void set_cpus(int prcs){
		if(prcs <= max_cpus)
			cpus = prcs;
	}
	
	/**
	 * @return
	 * Devuelve el número de cpus que se están utilizando actualmente.
	 */
	public int get_cpus(){
		return cpus;
	}
	
	/**
	 * @return
	 * Devuelve el máximo número de cpus disponibles en el servidor remoto.
	 */
	public int max_cpus(){
		return max_cpus;
	}
	
	/**
	 * Inicia el proceso de finalización de la conexión con el servidor remoto.
	 * 
	 * @return
	 * Devuelve el número de identificación de la transacción generada.
	 */
	public int FIN(){
		return objclient.request("FIN", null);
	}
	
	
	
	/* (non-Javadoc)
	 * @see android.remotematrixclient.objclient.clientevent.ObjEventListener
	 * #handleClientEvent(android.remotematrixclient.objclient.clientevent.ObjEvent)
	 */
	@Override
	public void handleClientEvent(ObjEvent evt) {
		if(evt.getSource() instanceof Receiver){
			CommandWrap cw = ircv.get();
			if(cw.ack){
				if(cw.cmd.equals("CPUS")){
					max_cpus = ((Integer)cw.data).intValue() - 1;
					cpus = max_cpus;
				}
				else
					trigger(cw);
			}
		}
	}
}
